﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Linq;
using System.Threading;
using Google.Protobuf;
using Cemit.PolyProto;
using System.Threading.Tasks;
using Cemit.Awaiter;
using Google.Protobuf.WellKnownTypes;

namespace Cemit.PolyServer
{
    public class PolyServer
    {
        const string VERSION = "0.1.10.0";
        
        protected virtual int PingInterval { get => 10; }
        protected virtual int TimeoutSeconds => PingInterval * 8;

        public Players Players { get; } = new Players();

        private IPEndPoint BindIP => new IPEndPoint(IPAddress.Parse(ip), port);

        private Dictionary<Socket, ClientState> clientStatePairs = new Dictionary<Socket, ClientState>();
        private List<Socket> m_Sockets = new List<Socket>();

        private string ip;
        public int port;
        private Socket listenfd;
        private SendAsyncState sendAsyncState = new SendAsyncState();
        private BufferHeap<Action<V>> asyncGetActionsHeap = new BufferHeap<Action<V>>(1024, 1024, 32768);

        /// <summary>
        /// 启动服务器
        /// </summary>
        public void Start(string listenIp, int listenPort)
        {
            ip = listenIp;
            port = listenPort;

            new Thread(StartLoop).Start();
            //return this;
        }

        protected virtual void Update()
        {
            CheckPing();
        }

        /// <summary>
        /// 启动服务器
        /// </summary>
        private void StartLoop()
        {
            this.Log($"PolyServer {VERSION}");

            listenfd = CreateTcpSocket();

            //绑定本地端口
            listenfd.Bind(BindIP);

            //开启监听，参数backlog指定队列中最多可容纳等待接受的连接数，0表示不限制
            listenfd.Listen(0);

            this.Log($"{GetType().Name}启动成功！");
            this.Log($"正在监听：{ip}:{port}");

            while (true)
            {
                //选择有状态刷新的套接字
                Socket.Select(GetSockets(), null, null, 1000);

                foreach (var item in m_Sockets)
                {
                    if (item == listenfd)
                    {
                        //读取监听套接字
                        ReadListenfd(item);
                    }
                    else
                    {
                        //读取客户端套接字
                        ReadClientfd(item);
                    }
                }

                Update();
            }

            List<Socket> GetSockets()
            {
                //获取当前的
                m_Sockets = clientStatePairs.Keys.ToList();
                m_Sockets.Add(listenfd);
                return m_Sockets;
            }
        }


        protected PolyServer AddSocketToSelectList(ClientState clientState)
        {
            clientStatePairs.Add(clientState.socket, clientState);
            return this;
        }

        protected Socket CreateTcpSocket()
        {
            return new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
        
        /// <summary>报告远程服务器返回的Get消息</summary>
        public void ReportAsyncGetAction(V msg)
        {
            asyncGetActionsHeap.Get(msg.Index)?.Invoke(msg);
            asyncGetActionsHeap.Free(msg.Index);
        }

        /// <summary>可等待的方法，返回远程服务器传回的消息</summary>
        /// <typeparam name="T">目标返回类型</typeparam>
        public AsyncAction<T> Get<T>(ClientState client, IMessage message) where T : IMessage, new()
        {
            AsyncAction<T> action = new AsyncAction<T>();

            long index = asyncGetActionsHeap.Add(report);

            Send(client, new G
            {
                Index = index,
                Msg = Any.Pack(message),
                Type = message.GetType().FullName
            });

            return action;

            void report(V msg)
            {
                action.ReportResult(msg.Msg.Unpack<T>());
            }
        }

        /// <summary>
        /// 向一个连接发送消息
        /// </summary>
        public void Send(ClientState state, IMessage message)
        {
            if (state.socket == null || !state.socket.Connected)
            {
                this.Log("消息发送失败：服务器未连接！");
                return;
            }

            Console.WriteLine($"发送：{message} To {state.ToInfoString()}");

            byte[] nameBytes = MessageEncoding.EncodeName(message);
            byte[] bodyBytes = MessageEncoding.Encode(message);

            int length = nameBytes.Length + bodyBytes.Length;
            byte[] sendBytes = new byte[2 + length];

            sendBytes[0] = (byte)(length % 256);
            sendBytes[1] = (byte)(length / 256);

            Array.Copy(nameBytes, 0, sendBytes, 2, nameBytes.Length);
            Array.Copy(bodyBytes, 0, sendBytes, 2 + nameBytes.Length, bodyBytes.Length);

            ByteArray byteArray = new ByteArray(sendBytes);

            sendAsyncState.client = state;
            sendAsyncState.message = message;

            try
            {
                state.socket.BeginSend(sendBytes, 0, sendBytes.Length,
                    0, SendCallback, sendAsyncState);
            }
            catch (Exception e)
            {
                this.Log("消息发送失败：" + e.Message);
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            sendAsyncState = (SendAsyncState)ar.AsyncState;
            ClientState state = sendAsyncState.client;
            IMessage message = sendAsyncState.message;

            int count = state.socket.EndSend(ar);

            if (!(message is Msg_Net_Pong))
            {
                Debug.Log($"发送信息：{message.GetType().Name} "
                    + $"{count} bytes"
                    + $" - {state.ToInfoString()}");
            }
        }

        /// <summary>
        /// 发送消息给某个玩家
        /// </summary>
        [System.Obsolete("使用Player.Send(Imessage)")]
        public void Send(string playerId, IMessage message)
        {
            if (!Players.Contains(playerId))
            {
                Debug.LogWarning($"发送消息失败：{playerId} 未在线。");
                return;
            }
            Players.GetPlayer(playerId).Send(message);
        }

        /// <summary>
        /// 向所有在线玩家发送消息
        /// </summary>
        public void SendToAllPlayers(IMessage message, params Player[] excepts)
        {
            var players = Players.GetPlayers().Where(IsNotExcept);
            foreach (var item in players)
            {
                item.Send(message);
            }

            bool IsNotExcept(Player player)
                => Array.IndexOf(excepts, player) == -1;
        }

        /// <summary>
        /// 从连接列表中删除某个套接字（不关闭该套接字的连接）
        /// </summary>
        public void RemoveSocket(Socket socket)
        {
            clientStatePairs.Remove(socket);
        }

        /// <summary>
        /// 关闭客户端
        /// </summary>
        [Obsolete("使用ClientState.Close()")]
        public void Close(ClientState client)
        {
            this.Log("关闭客户端：" + client.IP);

            clientStatePairs.Remove(client.socket);
            client.socket.Close();

            if (client.player != null)
            {
                Players.RemovePlayer(client.player);
            }
        }

        /// <summary>
        /// 读取客户端消息 
        /// </summary>
        private bool ReadClientfd(Socket socket)
        {
            ClientState state = clientStatePairs[socket];
            ByteArray readBuff = state.readBuff;

            //缓冲区不足，先接收信息然后整理字节。如果还是不足则代表单条信息过长，报错并关闭该连接。
            if (readBuff.Remain <= 0)
            {
                CheckAndFireMessage(state);
                readBuff.MoveBytes();

                if (readBuff.Remain <= 0)
                {
                    state.Close();
                    LogOutOfBufferError();
                    return false;
                }
            }

            int count;
            try
            {
                count = socket.Receive(readBuff.bytes, readBuff.writeIndex, readBuff.Remain, 0);
            }
            catch (Exception e)
            {
                state.Close();
                this.LogError($"接收数据错误：{e}。已强制关闭连接。");
                return false;
            }

            if (count == 0)
            {
                this.Log("客户端请求关闭：" + socket.RemoteEndPoint.ToString());
                state.Close();
                return false;
            }

            readBuff.writeIndex += count;

            CheckAndFireMessage(state);

            readBuff.CheckAndMoveBytes();

            return true;

            void LogOutOfBufferError()
                => this.LogError($"缓冲区溢出!单条协议最长长度为 {ByteArray.DEFAULT_SIZE}bytes。" +
                    "已强制关闭连接！");
        }

        /// <summary>读取新连接的客户端</summary>
        private void ReadListenfd(Socket listenfd)
        {
            try
            {
                Socket client = listenfd.Accept();

                ClientState state = new ClientState(client, this);
                clientStatePairs.Add(client, state);

                this.Log("成功连接：" + client.RemoteEndPoint.ToString());
            }
            catch (Exception e)
            {
                this.Log("接收连接申请失败：" + e.Message);
            }
        }

        /// <summary>检查是否字节缓存中已经有了完整的消息，有则取出并分发该消息</summary>
        private void CheckAndFireMessage(ClientState state)
        {
            //readBuff: 0904namehello
            ByteArray readBuff = state.readBuff;

            int msgLength = readBuff.ReadInt16();
            if (msgLength == 0 || readBuff.Length < msgLength)
            {
                return; //处理分包
            }
            readBuff.readIndex += 2;
            //readBuff: 04namehello

            string protoName = MessageEncoding.DecodeName(
                readBuff.bytes, readBuff.readIndex, out int nameCount);

            if (string.IsNullOrEmpty(protoName))
            {
                state.Close();
                this.Log("接收到错误的数据：无法识别消息名！" +
                    "已强行关闭连接");
                return;
            }

            readBuff.readIndex += nameCount;
            //readBuff: hello

            int bodyCount = msgLength - nameCount;
            byte[] bodyBytes = new byte[bodyCount];
            Array.Copy(readBuff.bytes, readBuff.readIndex, bodyBytes, 0, bodyCount);

            IMessage message = MessageEncoding.Decode(protoName, bodyBytes);

            readBuff.readIndex += bodyCount;
            readBuff.CheckAndMoveBytes();

            if (!(message is Msg_Net_Ping))
            {
                this.Log($"接收信息：{message.GetType().Name}" +
                    $" - {(state.IsLogin ? state.player.ID : state.IP.ToString())}");
            }
            //分发消息

            IMessageHandle method = message.GetHandle();

            if (method.IsNull())
            {
                this.Log($"找不到消息的接收者：" + message);
            }
            else
            {
                // 分发消息
                method.Execute(state, message);
            }

            // 还要数据时继续判定能否分发消息
            if (readBuff.Length > 2)
            {
                CheckAndFireMessage(state);
            }
        }

        /// <summary>
        /// 检查客户端是否超时，关闭超时的客户端
        /// </summary>
        private void CheckPing()
        {
            long stampNow = Time.GetTimeStamp();

            foreach (ClientState item in clientStatePairs.Values)
            {
                if (stampNow - item.lastPingTime > TimeoutSeconds)
                {
                    this.Log("连接超时，已断开连接：" + item.IP.ToString());
                    if (item.player != null)
                    {
                        Players.RemovePlayer(item.player);
                    }
                    item.Close();
                    return; //已删除客户端，继续遍历会导致出错
                }
            }
        }

        struct SendAsyncState
        {
            public ClientState client;
            public IMessage message;
        }
    }
}
